| Conditions | 63 |
| Paths | > 20000 |
| Total Lines | 251 |
| Lines | 0 |
| Ratio | 0 % |
| Tests | 54 |
| CRAP Score | 63 |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like ai.js ➔ nextBestMove often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | /** |
||
| 12 | function nextBestMove(board, boardSize) { |
||
| 13 | var m, |
||
| 14 | l, |
||
| 15 | position, |
||
| 16 | type, |
||
| 17 | line, |
||
| 18 | totalCells, |
||
| 19 | consecutiveCells, |
||
| 20 | emptySides, |
||
| 21 | bestScore, |
||
| 22 | cellScore, |
||
| 23 | directionScore, |
||
| 24 | score; |
||
| 25 | |||
| 26 | // iterate through all the board's cells |
||
| 27 | 16 | for (var i = boardSize * boardSize; i--; ) { |
|
| 28 | // skip to next cell if this cell is owned by the computer |
||
| 29 | // if (board[i] == 1) continue; |
||
| 30 | |||
| 31 | // by default, the best move is the first free cell |
||
| 32 | // (position, attack, defense) |
||
| 33 | |||
| 34 | 1600 | if (bestScore === undefined) { |
|
|
|
|||
| 35 | 16 | bestScore = [i, 0, 0]; |
|
| 36 | } |
||
| 37 | // we will give a "score" to the position, |
||
| 38 | // based on its surroundings horizontally, vertically and |
||
| 39 | // on both diagonals; for example: for a row like 000XXPXX000 (where "0" means empty, |
||
| 40 | // "X" represents the opponent's pieces and "P" is the position for which we are |
||
| 41 | // determining the "score", we would check |
||
| 42 | // 0|00XXP|XX000, 00|0XXPX|X000, 000|XXPXX|000, 000X|XPXX0|00, 000XX|PXX00|0, |
||
| 43 | // and then we would do the same on the vertical, and on both diagonals) |
||
| 44 | |||
| 45 | // cell's default score (attack and defense) |
||
| 46 | 1600 | cellScore = [0, 0]; |
|
| 47 | |||
| 48 | // the 4 directions to check: vertical, horizontal, diagonal /, diagonal \ (in this order) |
||
| 49 | 1600 | for (var j = 4; j--; ) { |
|
| 50 | // the default score for the direction we're checking |
||
| 51 | 6400 | directionScore = [0, 0]; |
|
| 52 | |||
| 53 | // check the 5 possible outcomes, as described above |
||
| 54 | // (if we're checking whether the player won, |
||
| 55 | // we'll do this iteration only once, checking for 5 in a row) |
||
| 56 | 6400 | for (var k = !board[i] ? 5 : 1; k--; ) { |
|
| 57 | // initialize the type of cells we're looking for, |
||
| 58 | // and the array with the cells on the current direction |
||
| 59 | 15680 | type = board[i] || undefined; |
|
| 60 | 15680 | line = []; |
|
| 61 | |||
| 62 | // check the 5 pieces for each possible outcome, plus the 2 sides |
||
| 63 | 15680 | for (l = 7; l--; ) { |
|
| 64 | // used to compute position |
||
| 65 | 73128 | m = -5 + k + l; |
|
| 66 | 73128 | var n = i % boardSize; |
|
| 67 | |||
| 68 | 73128 | if ( |
|
| 69 | // vertical |
||
| 70 | ((j === 0 && |
||
| 71 | (position = i + boardSize * m) !== false && |
||
| 72 | n == position % boardSize) || |
||
| 73 | // horizontal |
||
| 74 | (j == 1 && |
||
| 75 | (position = i + m) !== false && |
||
| 76 | ~~(position / boardSize) == ~~(i / boardSize)) || |
||
| 77 | // diagonal / |
||
| 78 | (j == 2 && |
||
| 79 | (position = i - boardSize * m + m) !== false && |
||
| 80 | ((position > i && position % boardSize < n) || |
||
| 81 | (position < i && position % boardSize > n) || |
||
| 82 | position == i)) || |
||
| 83 | // diagonal \ |
||
| 84 | ((j == 3 && |
||
| 85 | (position = i + boardSize * m + m) !== false && |
||
| 86 | ((position < i && position % boardSize < n) || |
||
| 87 | (position > i && position % boardSize > n))) || |
||
| 88 | position == i)) && |
||
| 89 | // the position is not off-board |
||
| 90 | position >= 0 && |
||
| 91 | position < boardSize * boardSize && |
||
| 92 | // the cell is of the same type as the ones we are looking for |
||
| 93 | // or, we are checking the score for an empty cell, |
||
| 94 | // the current position is empty, |
||
| 95 | // or is "undefined" (meaning we didn't yet find any non-empty cells) |
||
| 96 | (board[position] == type || |
||
| 97 | (!board[i] && (!board[position] || undefined === type)) || |
||
| 98 | // or we're just checking the sides |
||
| 99 | !l || |
||
| 100 | l == 6) |
||
| 101 | ) { |
||
| 102 | // add position to the line |
||
| 103 | 56572 | line.push(position); |
|
| 104 | |||
| 105 | // if we're not just checking the sides, |
||
| 106 | // this is not an empty cell, and is of the same type as |
||
| 107 | // the ones we're looking for, |
||
| 108 | // update the type of cells we're looking for |
||
| 109 | // (we use ^ instead of !=) |
||
| 110 | 56572 | if (l && l ^ 6 && undefined === type && board[position]) { |
|
| 111 | 1118 | type = board[position]; |
|
| 112 | } |
||
| 113 | |||
| 114 | // if the computed position is off-board, |
||
| 115 | // but this is a side - cell, save it as "undefined" |
||
| 116 | 16556 | } else if (!l || l == 6) { |
|
| 117 | 6532 | line.push(undefined); |
|
| 118 | } else { |
||
| 119 | // skip the rest of the test if we found this row to be "non-compliant" |
||
| 120 | // (a different type of cell than the ones we're looking for, |
||
| 121 | // one of the 5 cells is off - board) |
||
| 122 | 10024 | break; |
|
| 123 | } |
||
| 124 | } |
||
| 125 | |||
| 126 | // if we added exactly 7 position to our line, and |
||
| 127 | // the line is not containing * only * empty cells |
||
| 128 | 15680 | if (line.length == 7 && undefined !== type) { |
|
| 129 | // if we are checking whether the player won, |
||
| 130 | // set this flag so that later on we do not |
||
| 131 | // overwrite the value of the cell |
||
| 132 | 1326 | m = board[i] ? true : false; |
|
| 133 | |||
| 134 | // calculate the score when setting the current cell to |
||
| 135 | // the same type as the other ones we found |
||
| 136 | 1326 | board[i] = type; |
|
| 137 | |||
| 138 | // calculate the number of consecutive cells we get like this |
||
| 139 | // (we'll do this by looking in our "line" array) |
||
| 140 | 1326 | consecutiveCells = 0; |
|
| 141 | 1326 | totalCells = 0; |
|
| 142 | 1326 | emptySides = 0; |
|
| 143 | |||
| 144 | // the total number of cells of the same type |
||
| 145 | 1326 | for (l = 5; l--; ) { |
|
| 146 | 6630 | if (board[line[l + 1]] == type) { |
|
| 147 | 4570 | totalCells++; |
|
| 148 | } |
||
| 149 | } |
||
| 150 | |||
| 151 | // look to the left of the current cell |
||
| 152 | 1326 | for ( |
|
| 153 | l = line.indexOf(i) - 1; |
||
| 154 | l >= 0; |
||
| 155 | // if the cell is of the same type, |
||
| 156 | //increment the number of consecutive cells |
||
| 157 | l-- |
||
| 158 | ) { |
||
| 159 | 1528 | if (board[line[l]] == type) { |
|
| 160 | 718 | consecutiveCells++; |
|
| 161 | } else { |
||
| 162 | // otherwise |
||
| 163 | // if the adjacent cell is empty, increment the number of empty sides |
||
| 164 | // we have to use === 0 (instead of !) because it |
||
| 165 | // can also be "undefined" |
||
| 166 | 810 | if (board[line[l]] === 0) { |
|
| 167 | 646 | emptySides++; |
|
| 168 | } |
||
| 169 | |||
| 170 | // don't look further |
||
| 171 | 810 | break; |
|
| 172 | } |
||
| 173 | } |
||
| 174 | |||
| 175 | // look to the right of the current cell |
||
| 176 | 1326 | for ( |
|
| 177 | l = line.indexOf(i); |
||
| 178 | l < line.length; |
||
| 179 | // if the cell is of the same type, |
||
| 180 | // increment the number of consecutive cells |
||
| 181 | l++ |
||
| 182 | ) { |
||
| 183 | 5194 | if (board[line[l]] == type) { |
|
| 184 | 4370 | consecutiveCells++; |
|
| 185 | } else { |
||
| 186 | // otherwise |
||
| 187 | // if the adjacent cell is empty, increment the number of empty sides |
||
| 188 | // we have to use === 0 (instead of !) |
||
| 189 | // because it can also be "undefined" |
||
| 190 | 824 | if (board[line[l]] === 0) { |
|
| 191 | 652 | emptySides++; |
|
| 192 | } |
||
| 193 | |||
| 194 | // don't look further |
||
| 195 | 824 | break; |
|
| 196 | } |
||
| 197 | } |
||
| 198 | |||
| 199 | // give a score to the row based on the |
||
| 200 | // array below(number of cells / empty sides) |
||
| 201 | 1326 | score = [[0, 1], [2, 3], [4, 12], [10, 64], [256, 256]][ |
|
| 202 | consecutiveCells >= totalCells |
||
| 203 | ? Math.min(consecutiveCells, 5) - 1 |
||
| 204 | : totalCells - 1 |
||
| 205 | ][consecutiveCells >= totalCells ? (emptySides ? emptySides - 1 : 0) : 0]; |
||
| 206 | |||
| 207 | // reset the cell's value (unless we are looking to see if the player won) |
||
| 208 | 1326 | if (!m) { |
|
| 209 | 734 | board[i] = 0; |
|
| 210 | } |
||
| 211 | // } else if (score >= 256) { |
||
| 212 | // // if the player won, update the score |
||
| 213 | // score = 1024; |
||
| 214 | // } |
||
| 215 | |||
| 216 | // if, so far, this is the best |
||
| 217 | // attack / defense score(depending on the value of "type") |
||
| 218 | // for this direction, update it |
||
| 219 | 1326 | if (score > directionScore[type - 1]) { |
|
| 220 | 1016 | directionScore[type - 1] = score; |
|
| 221 | } |
||
| 222 | } |
||
| 223 | } |
||
| 224 | |||
| 225 | // update the cell's attack and defense score |
||
| 226 | // (we simply sum the best scores of all 4 directions) |
||
| 227 | 6400 | for (k = 2; k--; ) { |
|
| 228 | 12800 | cellScore[k] += directionScore[k]; |
|
| 229 | } |
||
| 230 | } |
||
| 231 | |||
| 232 | // used below |
||
| 233 | 1600 | j = cellScore[0] + cellScore[1]; |
|
| 234 | 1600 | k = bestScore[1] + bestScore[2]; |
|
| 235 | |||
| 236 | // if cell's attack + defense score is better than |
||
| 237 | // the current best attack and defense score |
||
| 238 | // or, cell's score is equal to the best score, |
||
| 239 | // but computer's move is better or equal to the player's, |
||
| 240 | // and the current best move is not *exactly* the same |
||
| 241 | 1600 | if ( |
|
| 242 | (j > k || (j == k && cellScore[0] >= bestScore[1])) && |
||
| 243 | // we're checking the score of an empty cell, |
||
| 244 | // or we're checking to see if the player won and he won |
||
| 245 | // (we don't update the score when checking if the |
||
| 246 | // player won * unless * the player actually won) |
||
| 247 | (!board[i] || cellScore[1] >= 1024) |
||
| 248 | |||
| 249 | // update best score (position, attack, defense) |
||
| 250 | ) { |
||
| 251 | 234 | bestScore = [i, cellScore[0], cellScore[1]]; |
|
| 252 | } |
||
| 253 | } |
||
| 254 | |||
| 255 | // Calculation done return x, y |
||
| 256 | var x, y; |
||
| 257 | |||
| 258 | 16 | y = Math.floor(bestScore[0] / boardSize); |
|
| 259 | 16 | x = bestScore[0] % boardSize; |
|
| 260 | |||
| 261 | 16 | return { x: x, y: y }; |
|
| 262 | } |
||
| 263 | |||
| 307 |